 
#ifndef __RSP_C_NET__HEADER_FILE__
#define __RSP_C_NET__HEADER_FILE__


#include "coid/comm/binstream/netstreamudpbuf.h"
#include "coid/comm/binstream/filestream.h"
#include "coid/comm/binstream/txtstream.h"
#include "coid/comm/hptimer.h"
#include "coid/comm/rnd.h"
#include "c_controller.h"



////////////////////////////////////////////////////////////////////////////////
///Client side networking controller
class rsp_c_net
{
#define CLIENT_AHEAD(usec)          (2*usec/4000)

public:
    rsp_c_controller _ctrl;         ///< sync controller
    

    rsp_c_net() : _net(0)
    {
        _cstatus = NOT_CONNECTED;

        _bdrawratehalf = true;
        _bsynctimer = false;
        _bcanmeasureping = false;

        _patched = 0;

        _nfailedsync = 0;
        _trip_delay = 0;

        MsecTimer::init();

        _slot = WMAX;

        _rnd.seed( _Timer.time() );
        _Timer.reset();
        _frame_usec = 16667;
        _Timer.set_period_usec(_frame_usec);

        _log.open(".logrspclient0?wctl");
        if( !_log.is_open() )
            _log.open(".logrspclient1?wctl");

        _logt.bind(_log);
        init_client_log(&_logt);
    }

    virtual ~rsp_c_net()
    {
        MsecTimer::term();
    }

    virtual ushort get_version() const = 0;

    virtual void draw_world() = 0;
    
    ///Called before syncing bound objects with the reference set
    virtual void pre_resync( bool initial )   {}
    ///Called after syncing bound objects with the reference set
    virtual void resync()           {}

    ///Called when disconnected from server
    virtual void on_disconnect()    {}


    bool is_connected() const       { return _slot != WMAX; }


    void send_sync()
    {
        _syncreq_id = _rnd.rand();
        _syncreq_time = _Timer.time();

        _net << (uint8)RSP_PACKET::SYNC << _syncreq_id << Grsp_version << get_version() << BINSTREAM_FLUSH;  //a sync request
        _bsynctimer = true;

        *TLOG << "SYNC send " << _syncreq_id << " at " << timefmt() << ", current trip delay: " << _trip_delay << "ms\n";
    }

    opcd recv_sync( bool firsttime )
    {
        _bsynctimer = false;

        uint ct = _Timer.time();
        uint trip_delay = ct - _syncreq_time;

        if(firsttime)
            *TLOG << _trip_delay << " received sync reply: ";

        uint server_time;
        ushort packet_size;
        uint8 ercd;
        _net >> ercd;

        if( ercd == RSP_PACKET::SYNC_VERSION_MISMATCH )
        {
            ushort ver_rsp, ver_room;
            _net >> ver_rsp >> ver_room;
            _net.acknowledge();
            return ersINVALID_VERSION;
        }
        if( ercd == RSP_PACKET::SYNC_OK )     //successfully synchronized
        {
            /*if( !firsttime  &&  int_abs(int(trip_delay-_trip_delay)) > 1 )
            {
                ++_nfailedsync;
                *TLOG << "MISMATCHED sync delay and long-run delay by: " << int_abs(int(trip_delay-_trip_delay)) << "ms\n";

                _syncreq_time = ct - MSEC_SYNC_REPLY_TIMEOUT;

                if( _nfailedsync < 4)
                {
                    _net.acknowledge(true);
                    return ersNOT_READY;
                }

                *TLOG << "RESETTING trip_delay after unsuccessful retries\n";
            }*/

            _nfailedsync = 0;
            _trip_delay = trip_delay;


            _net >> _frame_usec;
            _net >> server_time >> BINSTREAM_ACK;

            //_net.set_packet_size(packet_size);

            //ok, adjust the local clock
            // client timer precedes the server timer by half(or 3/4) of the frame time
            // so the frame on the client starts sooner by that amount
            uint st = server_time + _trip_delay/2 + CLIENT_AHEAD(_frame_usec);
            _Timer.set(st);
            _Timer.set_period_usec(_frame_usec);

            _time_lastsync = _syncreq_time = _Timer.time();

            *TLOG << "SYNC recv OK " << _syncreq_id << " at " << timefmt(ct,false) << " local time and "
                << timefmt() << " server time\n";

            if(firsttime)
                *TLOG << "ok\nsynchronized to the server clock, server time: " << timefmt(server_time,false)
                << ", trip delay: " << _trip_delay << ", frame_msec: " << (float(_frame_usec)/1000)
                << "\nresetting local timer to " << timefmt()
                << "\nsending the attach request ...\n";
            else if( int_abs(int(st-ct)) > 0 )
                *TLOG << "client timer adjusted by " << int(st-ct) << "ms, lag: " << _trip_delay/2 << "\n";

            return 0;
        }
        else
        {
            _net >> _frame_usec;
            *TLOG << "SYNC recv RETRY " << _syncreq_id << " at " << timefmt() << "\n";

            //push back ...
            _syncreq_time = ct - MSEC_SYNC_REPLY_TIMEOUT;

            _net.acknowledge();
            return ersNOT_READY;
        }
    }

    opcd connect( const token& nick, const token& addr, ushort port=32769, bool portoverride=false )
    {
        _cstatus = NOT_CONNECTED;

        _net.set_remote_address( addr, port, portoverride );

        uint ntimeout = N_RETRY_AFTER_TIMEOUT;
        uint nfailsync = N_RETRY_AFTER_FAILSYNC;

        *TLOG << "connecting to '" << addr << "'\n";

        //first, synchronize
        while(1)
        {
            send_sync();

            *TLOG << "(" << _Timer.time() << ") sending sync request\n";

            if( wait_reply( RSP_PACKET::SYNC, _syncreq_id, MSEC_SYNC_REPLY_TIMEOUT ) )
            {
                opcd e = recv_sync(true);
                if(!e)  break;

                if( e == ersINVALID_VERSION )
                    return e;
                    
                if( --nfailsync == 0 )
                {
                    *TLOG << "unable to synchronize\n";
                    return ersFAILED "unable to synchronize";
                }
                else
                {
                    *TLOG << "retry\n";
                    sysMilliSecondSleep( (_frame_usec + _frame_usec/3)/1000 );
                    continue;
                }
            }
            else if( --ntimeout == 0 )
            {
                *TLOG << "request timed out\n";
                return ersTIMEOUT;
            }
            else
                sysMilliSecondSleep(100);

            nfailsync = N_RETRY_AFTER_FAILSYNC;
        }

        uint rqid = _rnd.rand();

        //synchronized, now connect
        _net << (uint8)RSP_PACKET::CONNECT
            << rqid
            << (ushort)(_trip_delay/2)
            << _Timer.time()
            << nick
            << BINSTREAM_FLUSH;

        if( !wait_reply( RSP_PACKET::CONNECT, rqid, MSEC_CONNECT_REPLY_TIMEOUT ) ) {
            *TLOG << "attach request timed out\n";
            return ersTIMEOUT "in connect (after sync)";
        }

        opcd e;
        _net >> e;

        if(!e)
        {
            //server responded ok
            uint8 nws,nts;
            _net >> _ssid >> _slot >> nws >> nts;
            RSPC_SETTINGS.set_params(nws);
            _ctrl.set_refstateid( _Timer.ticks() );

            *TLOG << "attached to server, ssid: " << _ssid << ", slot id: " << _slot
                << ", wait states: " << nws << " OK\n";

            _cstatus = CONNECTED;
        }
        else
            *TLOG << "attach request failed, error: " << opcd_formatter(e) << "\n";
        _net >> BINSTREAM_ACK;

        return e;
    }

    void disconnect()
    {
        _net.reset();
        _net << (uint8)RSP_PACKET::DISCONNECT << _ssid << _slot << BINSTREAM_FLUSH;  //a sync request

        *TLOG << "DISCONNECT sent " << " at " << timefmt() << "\n";

        disconnect_silent();
    }

    void disconnect_silent()
    {
        _slot = WMAX;
        _patched = 0;

        _cstatus = DISCONNECTED;
        _ctrl.reset();

        on_disconnect();
    }

    bool process_frame();


private:

    bool wait_reply( uint8 packet, uint32 rqid, uint timeout )
    {
        uint ct = _Timer.time();

        for( uint n=0; n<50; ++n )
        {
            uint tmo = _Timer.time() - ct;
            if( tmo > timeout )
                tmo = 0;
            else
                tmo = timeout - tmo;

            if( _net.data_available(tmo) )
            {
                uint8 rt;
                _net >> rt;
                if( rt != packet )  { _net.acknowledge(true); continue; }

                uint32 rsid;
                _net >> rsid;
                if( rsid != rqid )  { _net.acknowledge(true); continue; }

                return true;
            }
        }

        return false;
    }

protected:
    netstreamudpbuf _net;
    rnd_strong _rnd;

    uint _frame_usec;               ///< milliseconds per frame, rcvd from server
    uint32 _ssid;
    uint16 _slot;                   ///< room & slot identifier

    struct txtstreamflush : txtstream
    {
        virtual void flush()
        {
            *this << "\n";
            txtstream::flush();
        }
    };

    fileiostream _log;
    txtstreamflush _logt;

    static uint MSEC_SYNC_REPLY_TIMEOUT;
    static uint MSEC_CONNECT_REPLY_TIMEOUT;
    static uint N_RETRY_AFTER_TIMEOUT;
    static uint N_RETRY_AFTER_FAILSYNC;
    static uint TIME_BETWEEN_SYNC;
    static uint CLIENT_TIMEOUT_FRAMES;

    int _cstatus;
    enum {
        DISCONNECTED                = -1,   ///< forcibly
        NOT_CONNECTED               = 0,    ///< initially

        CONNECTED                   = 1,
    };

    uint _patched;

    uint _syncreq_time;             ///< the time when client sent the sync request
    uint32 _syncreq_id;             ///< sync request id awaited

    uint _time_lastsync;            ///< last time when the client was synchronized with server
    int _trip_delay;

    uint _nfailedsync;              ///< number of failed sync attempts due to too different trip_delay

    bool _bsynctimer;               ///< true if we wait for sync reply from server during game
    bool _bcanmeasureping;          ///< true if we can measure ping - the message would be processed asap

protected:
    bool _bdrawratehalf;            ///< true if draw rate should be half the physics rate

};


#endif //__RSP_C_NET__HEADER_FILE__
